#ifndef __VECTOR4_H_INCL__
#define __VECTOR4_H_INCL__

#include <assert.h>
#include <ostream>

#include "Vector3.h"

/**
 * TODO: Add class description
 * 
 * @author   xiaoyu
 */
namespace PBVP {
	///
	class Vector4 {
	public:  
		float x, y, z, w;
		// Constructor
		Vector4() {}

		// Destructor
		~Vector4() {}

		// Copy constructor
		Vector4(const Vector4& vec)
			: x(vec.x), y(vec.y), z(vec.z), w(vec.w)
		{}

        Vector4( const float fX, const float fY, const float fZ, const float fW )
            : x( fX ), y( fY ), z( fZ ), w( fW)
        {}

        Vector4( const float coord[4] )
            : x( coord[0] ),
              y( coord[1] ),
              z( coord[2] ),
              w( coord[3] )
        {}

        Vector4( const int array[4] )
        {
            x = (float)array[0];
            y = (float)array[1];
            z = (float)array[2];
            w = (float)array[3];
        }

        Vector4( float* const r )
            : x( r[0] ), y( r[1] ), z( r[2] ), w( r[3] )
        {  }

        Vector4( const float scaler )
            : x( scaler )
            , y( scaler )
            , z( scaler )
            , w( scaler )
        {}

        Vector4(const Vector3& vec3)
            : x(vec3.x), y(vec3.y), z(vec3.z), w(1.0f)
        {}

		inline float operator [] ( const size_t i ) const
        {
            assert( i < 4 );
            return *(&x+i);
        }

		inline float& operator [] ( const size_t i )
        {
            assert( i < 4 );
            return *(&x+i);
        }

		/// Pointer accessor for direct copying
		inline float* ptr()
		{
			return &x;
		}
		/// Pointer accessor for direct copying
		inline const float* ptr() const
		{
			return &x;
		}

        /** Assigns the value of the other vector.
            @param vec The other vector
        */
        inline Vector4& operator = ( const Vector4& vec )
        {
            x = vec.x;
            y = vec.y;
            z = vec.z;
            w = vec.w;

            return *this;
        }

		inline Vector4& operator = ( const float fScalar)
		{
			x = fScalar;
			y = fScalar;
			z = fScalar;
			w = fScalar;
			return *this;
		}

        inline bool operator == ( const Vector4& vec ) const
        {
            return ( x == vec.x && y == vec.y &&
					 z == vec.z && w == vec.w );
        }

        inline bool operator != ( const Vector4& vec ) const
        {
            return ( x != vec.x || y != vec.y ||
					 z != vec.z || w != vec.w );
        }

        inline Vector4& operator = (const Vector3& rhs)
        {
            x = rhs.x;
            y = rhs.y;
            z = rhs.z;
            w = 1.0f;
            return *this;
        }

        // arithmetic operations
        inline Vector4 operator + ( const Vector4& vec ) const
        {
            return Vector4(
                x + vec.x,
                y + vec.y,
                z + vec.z,
                w + vec.w);
        }

        inline Vector4 operator - ( const Vector4& vec ) const
        {
            return Vector4(
                x - vec.x,
                y - vec.y,
                z - vec.z,
                w - vec.w);
        }

        inline Vector4 operator * ( const float fScalar ) const
        {
            return Vector4(
                x * fScalar,
                y * fScalar,
                z * fScalar,
                w * fScalar);
        }

        inline Vector4 operator * ( const Vector4& rhs) const
        {
            return Vector4(
                rhs.x * x,
                rhs.y * y,
                rhs.z * z,
                rhs.w * w);
        }

        inline Vector4 operator / ( const float fScalar ) const
        {
            assert( fScalar != 0.0 );

            float fInv = 1.0 / fScalar;

            return Vector4(
                x * fInv,
                y * fInv,
                z * fInv,
                w * fInv);
        }

        inline Vector4 operator / ( const Vector4& rhs) const
        {
            return Vector4(
                x / rhs.x,
                y / rhs.y,
                z / rhs.z,
                w / rhs.w);
        }

        inline const Vector4& operator + () const
        {
            return *this;
        }

        inline Vector4 operator - () const
        {
            return Vector4(-x, -y, -z, -w);
        }

        inline friend Vector4 operator * ( const float fScalar, const Vector4& vec )
        {
            return Vector4(
                fScalar * vec.x,
                fScalar * vec.y,
                fScalar * vec.z,
                fScalar * vec.w);
        }

        inline friend Vector4 operator / ( const float fScalar, const Vector4& vec )
        {
            return Vector4(
                fScalar / vec.x,
                fScalar / vec.y,
                fScalar / vec.z,
                fScalar / vec.w);
        }

        inline friend Vector4 operator + (const Vector4& lhs, const float rhs)
        {
            return Vector4(
                lhs.x + rhs,
                lhs.y + rhs,
                lhs.z + rhs,
                lhs.w + rhs);
        }

        inline friend Vector4 operator + (const float lhs, const Vector4& rhs)
        {
            return Vector4(
                lhs + rhs.x,
                lhs + rhs.y,
                lhs + rhs.z,
                lhs + rhs.w);
        }

        inline friend Vector4 operator - (const Vector4& lhs, float rhs)
        {
            return Vector4(
                lhs.x - rhs,
                lhs.y - rhs,
                lhs.z - rhs,
                lhs.w - rhs);
        }

        inline friend Vector4 operator - (const float lhs, const Vector4& rhs)
        {
            return Vector4(
                lhs - rhs.x,
                lhs - rhs.y,
                lhs - rhs.z,
                lhs - rhs.w);
        }

        // arithmetic updates
        inline Vector4& operator += ( const Vector4& vec )
        {
            x += vec.x;
            y += vec.y;
            z += vec.z;
            w += vec.w;

            return *this;
        }

        inline Vector4& operator -= ( const Vector4& vec )
        {
            x -= vec.x;
            y -= vec.y;
            z -= vec.z;
            w -= vec.w;

            return *this;
        }

        inline Vector4& operator *= ( const float fScalar )
        {
            x *= fScalar;
            y *= fScalar;
            z *= fScalar;
            w *= fScalar;
            return *this;
        }

        inline Vector4& operator += ( const float fScalar )
        {
            x += fScalar;
            y += fScalar;
            z += fScalar;
            w += fScalar;
            return *this;
        }

        inline Vector4& operator -= ( const float fScalar )
        {
            x -= fScalar;
            y -= fScalar;
            z -= fScalar;
            w -= fScalar;
            return *this;
        }

        inline Vector4& operator *= ( const Vector4& vec )
        {
            x *= vec.x;
            y *= vec.y;
            z *= vec.z;
            w *= vec.w;

            return *this;
        }

        inline Vector4& operator /= ( const float fScalar )
        {
            assert( fScalar != 0.0 );

            float fInv = 1.0 / fScalar;

            x *= fInv;
            y *= fInv;
            z *= fInv;
            w *= fInv;

            return *this;
        }

        inline Vector4& operator /= ( const Vector4& vec )
        {
            x /= vec.x;
            y /= vec.y;
            z /= vec.z;
            w /= vec.w;

            return *this;
        }

        /** Calculates the dot (scalar) product of this vector with another.
            @param
                vec Vector with which to calculate the dot product (together
                with this one).
            @returns
                A float representing the dot product value.
        */
        inline float dotProduct(const Vector4& vec) const
        {
            return x * vec.x + y * vec.y + z * vec.z + w * vec.w;
        }
        /** Function for writing to a stream.
        */
        friend std::ostream& operator << ( std::ostream& o, const Vector4& v )
        {
            o << "Vector4(" << v.x << ", " << v.y << ", " << v.z << ", " << v.w << ")";
            return o;
        }
        // special
        static const Vector4 ZERO;
	};

}
#endif // __VECTOR4_H_INCL__
